Skip to content

fix: UI polish — settings, chat, overscroll, session badges#32

Merged
wilcorrea merged 3 commits into
mainfrom
fix/visual-polish
Mar 2, 2026
Merged

fix: UI polish — settings, chat, overscroll, session badges#32
wilcorrea merged 3 commits into
mainfrom
fix/visual-polish

Conversation

@wilcorrea

@wilcorrea wilcorrea commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Settings window: remove duplicate "Check Permissions" button, make tabs full-width with equal distribution, remove redundant title from window title bar
  • Home screen: add session count badge on workspace cards (new count_workspace_sessions Tauri command), matching the existing comment count badge pattern
  • Terminal chat: fix dot indicator alignment using line-height + vertical-align instead of magic margin-top values; set min-width on Send/Stop button to prevent layout shift
  • General: disable macOS rubber-band overscroll via overscroll-behavior: none; close review panel automatically on plan approval

Test plan

  • Open Settings — verify tabs span full width equally, no "Settings" in title bar
  • Settings > Voice-to-Text — verify single "Check Permissions" button below microphone dropdown
  • Home screen — verify workspace cards show session count badge (top-right corner)
  • Open a session chat — verify colored dots align with first line of text (agent, tool, thinking messages)
  • During streaming — verify Send/Stop button doesn't shift in size
  • Scroll to the end of home screen content — verify no elastic bounce/overscroll
  • Approve a plan in review mode — verify review panel closes automatically

Summary by CodeRabbit

  • New Features

    • Session count badges now display on workspace cards and per-workspace session counts are fetched.
  • Bug Fixes

    • Review panel now reliably closes after approving a review.
  • Style

    • Improved alignment and spacing in terminal messages and dot indicators.
    • Ensured consistent button minimum widths and expanded tab layout.
    • Refined microphone permissions UI in settings and removed the settings window title.
    • Updated page scroll behavior for smoother experience.

- Remove duplicate "Check Permissions" button in WhisperSettings
- Make settings tabs full-width with equal distribution
- Remove "Settings" text from window title bar
- Add session count badge on workspace cards
- Disable macOS rubber-band overscroll
- Fix terminal chat dot alignment with text baseline
- Close review panel on plan approval
- Set min-width on Send/Stop button to prevent layout shift
@coderabbitai

coderabbitai Bot commented Mar 2, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@wilcorrea has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e7a7d35 and 4c54b2c.

📒 Files selected for processing (2)
  • apps/tauri/src-tauri/src/sessions.rs
  • apps/tauri/src/components/MarkdownViewer.tsx
📝 Walkthrough

Walkthrough

Adds backend session-counting commands and exposes them to the Tauri frontend; the HomeScreen fetches per-workspace session counts and passes them to WorkspaceCard for badge rendering. Also includes minor UI styling and settings window title changes.

Changes

Cohort / File(s) Summary
Backend session command
apps/tauri/src-tauri/src/lib.rs, apps/tauri/src-tauri/src/sessions.rs
Register new Tauri command count_workspace_sessions; add count_sessions_batch helper to query per-workspace session counts from the DB.
Frontend session integration
apps/tauri/src/components/HomeScreen.tsx, apps/tauri/src/components/WorkspaceCard.tsx
HomeScreen fetches session counts for directory workspaces and stores them in state; WorkspaceCard gains optional sessionCount prop and renders a MessagesSquare badge when >0.
Settings window title & tabs layout
apps/tauri/src-tauri/tauri.conf.json, apps/tauri/src/SettingsApp.tsx, apps/tauri/src/components/ui/tabs.tsx
Settings window title cleared in config and runtime title setter removed; Tabs trigger made flex-1 and TabsList given full width.
Markdown review panel behavior
apps/tauri/src/components/MarkdownViewer.tsx
After approving a review, the review panel is collapsed and its open state set to false; useComments exposes setIsPanelOpen.
UI styling tweaks
apps/tauri/src/components/TerminalInput.tsx, apps/tauri/src/components/TerminalMessage.tsx, apps/tauri/src/index.css
Button min-width added, dot-wrapper class introduced and dot alignment adjusted, terminal message alignment refined in CSS.
Whisper settings UI
apps/tauri/src/components/settings/WhisperSettings.tsx
Reworked microphone permissions UI: replaced combined icon/button with separate Select and small Mic button; removed Shield icon import and previous check-permissions block.

Sequence Diagram

sequenceDiagram
    participant HS as HomeScreen
    participant Tauri as Tauri Layer
    participant DB as Database
    participant WC as WorkspaceCard

    HS->>Tauri: invoke("count_workspace_sessions", workspace_paths)
    Tauri->>DB: count_sessions_batch(conn, workspace_paths)
    DB-->>Tauri: Vec<(path, count)>
    Tauri-->>HS: Result<Vec<(String,i64)>, String>
    HS->>HS: set sessionCounts state
    HS->>WC: render WorkspaceCard(sessionCount)
    WC->>WC: show session badge if sessionCount > 0
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I nibble rows of session seeds,
counting paths with little leaps,
badges bloom where numbers grow,
backend hops and frontends glow —
joyous snares of workspace peeps!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: UI polish across settings, chat, overscroll behavior, and session badges on workspace cards.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/visual-polish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/tauri/src/components/MarkdownViewer.tsx (1)

200-207: ⚠️ Potential issue | 🟠 Major

setIsPanelOpen(false) alone won’t close the visible panel

Line 205 updates review state, but this component collapses the UI via reviewRef.current?.collapse() (and onCollapse). With current code, approve can leave the panel visually open, missing the PR objective.

Suggested fix
   const handleApprove = useCallback(() => {
     if (!onApprovePlan) return;
     const reviewText = review.generateReview();
     const hasComments = review.comments.some((c) => !c.resolved);
     review.resolveAll();
-    review.setIsPanelOpen(false);
+    reviewRef.current?.collapse();
+    review.setIsPanelOpen(false);
     onApprovePlan(hasComments ? reviewText : undefined);
   }, [review.generateReview, review.comments, review.resolveAll, review.setIsPanelOpen, onApprovePlan]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src/components/MarkdownViewer.tsx` around lines 200 - 207, The
approve handler (handleApprove) updates review state via
review.setIsPanelOpen(false) but does not actually collapse the visible panel;
call the UI collapse method to ensure the panel is closed visually. Modify
handleApprove to invoke reviewRef.current?.collapse() (or call onCollapse())
after resolving comments and before/after setIsPanelOpen(false), and add
reviewRef (or onCollapse) to the hook dependencies so the ref/callback is
referenced correctly; ensure you null-check reviewRef.current?.collapse before
calling it.
🧹 Nitpick comments (3)
apps/tauri/src-tauri/src/sessions.rs (1)

177-191: Consider batching queries for efficiency.

The current implementation executes N separate queries (one per workspace path). For typical usage with a small number of workspaces, this is fine. However, if the workspace count grows, a single query with an IN clause would be more efficient.

♻️ Optional: Single-query approach
 pub fn count_sessions_batch(conn: &Connection, workspace_paths: &[String]) -> Result<Vec<(String, i64)>, String> {
-    let mut results = Vec::with_capacity(workspace_paths.len());
-    let mut stmt = conn
-        .prepare("SELECT COUNT(*) FROM sessions WHERE workspace_path = ?1")
-        .map_err(|e| format!("Prepare error: {}", e))?;
-    for path in workspace_paths {
-        let count: i64 = stmt
-            .query_row(params![path], |row| row.get(0))
-            .unwrap_or(0);
-        if count > 0 {
-            results.push((path.clone(), count));
-        }
-    }
-    Ok(results)
+    if workspace_paths.is_empty() {
+        return Ok(Vec::new());
+    }
+    let placeholders: Vec<_> = (1..=workspace_paths.len()).map(|i| format!("?{}", i)).collect();
+    let sql = format!(
+        "SELECT workspace_path, COUNT(*) FROM sessions WHERE workspace_path IN ({}) GROUP BY workspace_path HAVING COUNT(*) > 0",
+        placeholders.join(", ")
+    );
+    let mut stmt = conn.prepare(&sql).map_err(|e| format!("Prepare error: {}", e))?;
+    let params: Vec<&dyn rusqlite::ToSql> = workspace_paths.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
+    let rows = stmt
+        .query_map(params.as_slice(), |row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)))
+        .map_err(|e| format!("Query error: {}", e))?
+        .collect::<Result<Vec<_>, _>>()
+        .map_err(|e| format!("Row error: {}", e))?;
+    Ok(rows)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src-tauri/src/sessions.rs` around lines 177 - 191, The current
count_sessions_batch runs one query per workspace_path; change it to issue a
single SQL query using "SELECT workspace_path, COUNT(*) FROM sessions WHERE
workspace_path IN (?1,?2,...) GROUP BY workspace_path" to improve performance:
in count_sessions_batch build dynamic placeholders matching
workspace_paths.len(), return early with empty vec if workspace_paths is empty,
prepare that single statement (instead of the existing per-path stmt), bind all
workspace_paths as params, iterate over rows to push (workspace_path, count)
into results, and preserve the same Result<Vec<(String,i64)>, String> error
handling.
apps/tauri/src/components/HomeScreen.tsx (1)

38-51: Dependency array pattern creates unnecessary closures.

The dirWorkspaces.map((w) => w.path).join(",") in the dependency array (line 51) is evaluated on every render before comparison. While the resulting string is stable when paths don't change, the intermediate .map() call allocates a new array each time. Consider memoizing the paths or moving the filtering logic inside the callback.

The same pattern exists in fetchCommentCounts (line 36). Both could be simplified:

♻️ Suggested improvement using useMemo
+  const dirPaths = useMemo(() => dirWorkspaces.map((w) => w.path), [dirWorkspaces]);
+  const filePaths = useMemo(() => fileWorkspaces.map((w) => w.path), [fileWorkspaces]);

   const fetchCommentCounts = useCallback(async () => {
-    if (fileWorkspaces.length === 0) return;
-    const paths = fileWorkspaces.map((w) => w.path);
+    if (filePaths.length === 0) return;
     try {
-      const results = await invoke<[string, number][]>("count_unresolved_comments", { filePaths: paths });
+      const results = await invoke<[string, number][]>("count_unresolved_comments", { filePaths });
       // ...
     } catch {
       // silently ignore
     }
-  }, [fileWorkspaces.map((w) => w.path).join(",")]);
+  }, [filePaths]);

   const fetchSessionCounts = useCallback(async () => {
-    if (dirWorkspaces.length === 0) return;
-    const paths = dirWorkspaces.map((w) => w.path);
+    if (dirPaths.length === 0) return;
     try {
-      const results = await invoke<[string, number][]>("count_workspace_sessions", { workspacePaths: paths });
+      const results = await invoke<[string, number][]>("count_workspace_sessions", { workspacePaths: dirPaths });
       // ...
     } catch {
       // silently ignore
     }
-  }, [dirWorkspaces.map((w) => w.path).join(",")]);
+  }, [dirPaths]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src/components/HomeScreen.tsx` around lines 38 - 51, The
dependency array currently calls dirWorkspaces.map(...).join(",") which
allocates on every render and causes unnecessary closures in fetchSessionCounts
(and similarly fetchCommentCounts); fix by memoizing the derived paths with
useMemo (e.g. const workspacePaths = useMemo(() => dirWorkspaces.map(w =>
w.path), [dirWorkspaces]) and then use workspacePaths (or
workspacePaths.join(",")) in the dependency arrays, or alternatively move the
mapping inside fetchSessionCounts and depend directly on dirWorkspaces to avoid
the repeated map allocation. Ensure you update both fetchSessionCounts and
fetchCommentCounts to reference the memoized workspacePaths or dirWorkspaces
consistently.
apps/tauri/src/components/WorkspaceCard.tsx (1)

86-98: Badge positioning relies on mutual exclusivity.

Both badges use identical absolute top-1.5 right-1.5 positioning. Currently this works because unresolvedComments is only passed to file workspaces and sessionCount to directory workspaces (they're mutually exclusive). However, this creates a maintenance hazard if future changes allow both badges on the same card.

Consider making this explicit with a comment, or restructuring to handle potential co-existence:

💡 Option: Add clarifying comment
+      {/* Note: unresolvedComments and sessionCount are mutually exclusive by workspace type */}
       {unresolvedComments != null && unresolvedComments > 0 && (
         <span className="absolute top-1.5 right-1.5 inline-flex items-center gap-0.5 text-muted-foreground group-hover:opacity-0 transition-opacity">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src/components/WorkspaceCard.tsx` around lines 86 - 98, The two
badges (unresolvedComments and sessionCount) in WorkspaceCard share identical
absolute positioning ("absolute top-1.5 right-1.5") which will cause overlap if
both become present; update WorkspaceCard to either (a) render a single
top-right badge container and place both badges inside it using a vertical or
horizontal layout with spacing (e.g., a flex column/row with gap) so they don't
collide, or (b) give each badge distinct offsets (e.g., top-1.5 right-1.5 and
top-1.5 right-6) so they stack, and add a brief inline comment above the badge
rendering explaining the choice; ensure you reference and conditionally render
unresolvedComments and sessionCount inside that adjusted container.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/tauri/src/components/MarkdownViewer.tsx`:
- Around line 200-207: The approve handler (handleApprove) updates review state
via review.setIsPanelOpen(false) but does not actually collapse the visible
panel; call the UI collapse method to ensure the panel is closed visually.
Modify handleApprove to invoke reviewRef.current?.collapse() (or call
onCollapse()) after resolving comments and before/after setIsPanelOpen(false),
and add reviewRef (or onCollapse) to the hook dependencies so the ref/callback
is referenced correctly; ensure you null-check reviewRef.current?.collapse
before calling it.

---

Nitpick comments:
In `@apps/tauri/src-tauri/src/sessions.rs`:
- Around line 177-191: The current count_sessions_batch runs one query per
workspace_path; change it to issue a single SQL query using "SELECT
workspace_path, COUNT(*) FROM sessions WHERE workspace_path IN (?1,?2,...) GROUP
BY workspace_path" to improve performance: in count_sessions_batch build dynamic
placeholders matching workspace_paths.len(), return early with empty vec if
workspace_paths is empty, prepare that single statement (instead of the existing
per-path stmt), bind all workspace_paths as params, iterate over rows to push
(workspace_path, count) into results, and preserve the same
Result<Vec<(String,i64)>, String> error handling.

In `@apps/tauri/src/components/HomeScreen.tsx`:
- Around line 38-51: The dependency array currently calls
dirWorkspaces.map(...).join(",") which allocates on every render and causes
unnecessary closures in fetchSessionCounts (and similarly fetchCommentCounts);
fix by memoizing the derived paths with useMemo (e.g. const workspacePaths =
useMemo(() => dirWorkspaces.map(w => w.path), [dirWorkspaces]) and then use
workspacePaths (or workspacePaths.join(",")) in the dependency arrays, or
alternatively move the mapping inside fetchSessionCounts and depend directly on
dirWorkspaces to avoid the repeated map allocation. Ensure you update both
fetchSessionCounts and fetchCommentCounts to reference the memoized
workspacePaths or dirWorkspaces consistently.

In `@apps/tauri/src/components/WorkspaceCard.tsx`:
- Around line 86-98: The two badges (unresolvedComments and sessionCount) in
WorkspaceCard share identical absolute positioning ("absolute top-1.5
right-1.5") which will cause overlap if both become present; update
WorkspaceCard to either (a) render a single top-right badge container and place
both badges inside it using a vertical or horizontal layout with spacing (e.g.,
a flex column/row with gap) so they don't collide, or (b) give each badge
distinct offsets (e.g., top-1.5 right-1.5 and top-1.5 right-6) so they stack,
and add a brief inline comment above the badge rendering explaining the choice;
ensure you reference and conditionally render unresolvedComments and
sessionCount inside that adjusted container.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40282fb and 2f9d0a2.

📒 Files selected for processing (12)
  • apps/tauri/src-tauri/src/lib.rs
  • apps/tauri/src-tauri/src/sessions.rs
  • apps/tauri/src-tauri/tauri.conf.json
  • apps/tauri/src/SettingsApp.tsx
  • apps/tauri/src/components/HomeScreen.tsx
  • apps/tauri/src/components/MarkdownViewer.tsx
  • apps/tauri/src/components/TerminalInput.tsx
  • apps/tauri/src/components/TerminalMessage.tsx
  • apps/tauri/src/components/WorkspaceCard.tsx
  • apps/tauri/src/components/settings/WhisperSettings.tsx
  • apps/tauri/src/components/ui/tabs.tsx
  • apps/tauri/src/index.css

- Collapse review panel visually on plan approval via reviewRef
- Optimize count_sessions_batch to single SQL query with IN clause
- Memoize workspace paths in HomeScreen to avoid render allocations
@wilcorrea

Copy link
Copy Markdown
Contributor Author

Review feedback addressed in e7a7d35

1. MarkdownViewer.tsx — handleApprove panel collapse (CAUTION)
Added reviewRef.current?.collapse() before setIsPanelOpen(false) so the review panel visually closes on plan approval. The onCollapse callback on the ResizablePanel also calls setIsPanelOpen(false), so the explicit call serves as a defensive fallback.

2. sessions.rs — count_sessions_batch optimization (nitpick)
Replaced the N-query loop with a single SQL query using IN clause + GROUP BY + HAVING COUNT(*) > 0. Early-returns on empty input.

3. HomeScreen.tsx — dependency array allocations (nitpick)
Extracted filePaths and dirPaths into useMemo hooks. Both fetchCommentCounts and fetchSessionCounts now reference these memoized arrays instead of calling .map().join() in dependency arrays on every render.

4. WorkspaceCard.tsx — badge positioning (nitpick)
No change needed. The two badges (unresolvedComments for file workspaces, sessionCount for directory workspaces) are mutually exclusive by workspace type — they never render on the same card.

5. tabs.tsx — flex-1 on TabsTrigger (not flagged)
Validated that Tabs is only used in SettingsApp.tsx, so the global flex-1 default is safe. Future tab usages can override via className="flex-none" if needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/tauri/src-tauri/src/sessions.rs`:
- Around line 177-187: count_sessions_batch currently builds one SQL statement
with one placeholder per workspace path which can exceed SQLite's bind-parameter
limit; fix by chunking workspace_paths into batches sized to the connection's
variable limit (use conn.limit(rusqlite::Limit::SQLITE_LIMIT_VARIABLE_NUMBER)
and fall back to 999 if None), for each chunk build the placeholders, prepare
and execute the SELECT ... IN (...) query (using the same logic in
count_sessions_batch but per chunk), collect each chunk's (workspace_path,
count) rows into a HashMap keyed by workspace_path summing counts across chunks,
then return the aggregated Vec<(String, i64)>; ensure you construct params for
each chunk and re-prepare the statement per chunk (refer to function name
count_sessions_batch and use rusqlite::Limit::SQLITE_LIMIT_VARIABLE_NUMBER).

In `@apps/tauri/src/components/MarkdownViewer.tsx`:
- Around line 205-206: The close-on-approve is reverted because the effect that
expands the panel (condition embedded && phase === "reviewing") runs on every
render and can reopen immediately after reviewRef.current?.collapse() and
review.setIsPanelOpen(false); to fix this, change the effect that opens the
panel (the one testing embedded && phase === "reviewing") to only trigger when
entering the reviewing phase by comparing current phase to a stored previous
phase (e.g., prevPhaseRef) or by checking prevPhase !== "reviewing" before
calling the open logic; update the prevPhaseRef after handling so that functions
like reviewRef.current?.collapse() and review.setIsPanelOpen(false) are not
immediately undone.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9d0a2 and e7a7d35.

📒 Files selected for processing (3)
  • apps/tauri/src-tauri/src/sessions.rs
  • apps/tauri/src/components/HomeScreen.tsx
  • apps/tauri/src/components/MarkdownViewer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/tauri/src/components/HomeScreen.tsx

Comment thread apps/tauri/src-tauri/src/sessions.rs Outdated
Comment thread apps/tauri/src/components/MarkdownViewer.tsx
- Chunk workspace_paths into batches of 999 in count_sessions_batch
  to stay under SQLite's SQLITE_MAX_VARIABLE_NUMBER limit
- Track previous phase with a ref so the useEffect that expands
  the review panel only triggers on transition into "reviewing",
  preventing it from immediately reverting the close-on-approve
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant